{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Conway's Game of Life\n",
    "This notebook demostrates how you can develop interactive notebooks with lgo by developping [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "// Board defines the interface of game-of-life board.\n",
    "// This interface exists to remove the direct dependency between Board implementation and renderer.\n",
    "type Board interface {\n",
    "    Generation() int\n",
    "    Get(x, y int) bool\n",
    "    Set(x, y int)\n",
    "    Size() (int, int)\n",
    "    Next()\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "type boardImpl struct {\n",
    "    cur [][]bool\n",
    "    buf [][]bool\n",
    "    generation int\n",
    "}\n",
    "\n",
    "func NewBoard(w, h int) *boardImpl {\n",
    "    if w < 3 || h < 3 {\n",
    "        panic(\"too small\")\n",
    "    }\n",
    "    cur := make([][]bool, w)\n",
    "    buf := make([][]bool, w)\n",
    "    for i := 0; i < w; i++ {\n",
    "        cur[i] = make([]bool, h)\n",
    "        buf[i] = make([]bool, h)\n",
    "    }\n",
    "    return &boardImpl{\n",
    "        cur: cur,\n",
    "        buf: buf,\n",
    "    }\n",
    "}\n",
    "\n",
    "func (b *boardImpl) Set(x, y int) {\n",
    "    if x >= 1 && y >= 1 && x < len(b.cur)-1 && y < len(b.cur[0])-1 {\n",
    "        b.cur[x][y] = true\n",
    "    }\n",
    "}\n",
    "\n",
    "func (b *boardImpl) Get(x, y int) bool {\n",
    "    return b.cur[x][y]\n",
    "}\n",
    "\n",
    "func (b *boardImpl) Size() (int, int) {\n",
    "    return len(b.cur), len(b.cur[0])\n",
    "}\n",
    "\n",
    "func (b *boardImpl) Generation() int {\n",
    "    return b.generation\n",
    "}\n",
    "\n",
    "func (b *boardImpl) nextPixel(x, y int) bool {\n",
    "    c := 0\n",
    "    for i := x-1; i < x+2; i++ {\n",
    "        for j := y-1; j < y+2; j++ {\n",
    "            if (i != x || j != y) && b.cur[i][j] {\n",
    "                c += 1\n",
    "            }\n",
    "        }\n",
    "    }\n",
    "    if !b.cur[x][y] {\n",
    "        // dead\n",
    "        return c == 3\n",
    "    }\n",
    "    if c == 2 || c == 3 {\n",
    "        return true\n",
    "    }\n",
    "    return false\n",
    "}\n",
    "\n",
    "func (b *boardImpl) Next() {\n",
    "    w := len(b.cur)\n",
    "    h := len(b.cur[0])\n",
    "    for i := 0; i < w; i++ {\n",
    "        for j := 0; j < h; j++ {\n",
    "            if i == 0 || i == w-1 || j == 0 || j == h-1 {\n",
    "                // border\n",
    "                b.buf[i][j] = false\n",
    "                continue\n",
    "            }\n",
    "            b.buf[i][j] = b.nextPixel(i, j)\n",
    "        }\n",
    "    }\n",
    "    tmp := b.cur\n",
    "    b.cur = b.buf\n",
    "    b.buf = tmp\n",
    "    b.generation++\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import (\n",
    "    \"bytes\"\n",
    "    \"encoding/base64\"\n",
    "    \"fmt\"\n",
    "    \"math/rand\"\n",
    "    \"time\"\n",
    "    \"os\"\n",
    ")\n",
    "\n",
    "// Canvas renders the content of GameOfLife to HTML Canvas.\n",
    "type Canvas struct {\n",
    "    id string\n",
    "    jsid string\n",
    "    width int\n",
    "    height int\n",
    "    board Board\n",
    "    labelID string\n",
    "    svgID string\n",
    "}\n",
    "\n",
    "func NewCanvas(board Board, width, height int) *Canvas {\n",
    "    return &Canvas{\n",
    "        id: fmt.Sprintf(\"canvas%d\", rand.Int63()),\n",
    "        width: width,\n",
    "        height: height,\n",
    "        board: board,\n",
    "    }\n",
    "}\n",
    "\n",
    "func (c *Canvas) renderSVG() {\n",
    "    board := c.board\n",
    "    w, h := board.Size()\n",
    "    cw := 100/float64(w)\n",
    "    ch := 100/float64(h)\n",
    "    var buf bytes.Buffer\n",
    "    buf.WriteString(`<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"100\" height=\"100\">`)\n",
    "    for x := 0; x < w; x++ {\n",
    "        for y := 0; y < h; y++ {\n",
    "            if !board.Get(x, y) {\n",
    "                continue\n",
    "            }\n",
    "            buf.WriteString(fmt.Sprintf(\n",
    "                `<rect x=\"%.2f\" y=\"%.2f\" width=\"%.2f\" height=\"%.2f\"></rect>`,\n",
    "                float64(x)*cw, float64(y)*ch, cw, ch))\n",
    "        }\n",
    "    }\n",
    "    buf.WriteString(`</svg>`)\n",
    "    _ctx.Display.Text(fmt.Sprintf(\"Generation: %d\", board.Generation()), &c.labelID)\n",
    "    _ctx.Display.HTML(fmt.Sprintf(\n",
    "        `<img style=\"width:%dpx;height:%dpx\" src=\"data:image/svg+xml;base64,%s\">`,\n",
    "        c.width, c.height,\n",
    "        base64.StdEncoding.EncodeToString(buf.Bytes())), &c.svgID)\n",
    "}\n",
    "\n",
    "func (c *Canvas) DisplayAnimation(step int, interval time.Duration) {\n",
    "    if interval < 10 * time.Millisecond {\n",
    "        fmt.Fprintf(os.Stderr, \"interval is too small: %v\", interval)\n",
    "        return\n",
    "    }\n",
    "    c.renderSVG()\n",
    "    prev := time.Now()\n",
    "    for i := 0; step < 0 || i < step; i++ {\n",
    "        c.board.Next()\n",
    "        time.Sleep(interval-time.Now().Sub(prev))\n",
    "        prev = time.Now()\n",
    "        c.renderSVG()\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Oscillators"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Generation: 20"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img style=\"width:400px;height:200px\" src=\"\">"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "{\n",
    "    g := NewBoard(20, 10)\n",
    "    c := NewCanvas(g, 400, 200)\n",
    "    \n",
    "    var x, y int\n",
    "    x, y = 1, 1\n",
    "    g.Set(x, y+1)\n",
    "    g.Set(x+1, y+1)\n",
    "    g.Set(x+2, y+1)\n",
    "    \n",
    "    x, y = 5, 1\n",
    "    g.Set(x+1, y+1)\n",
    "    g.Set(x+2, y+1)\n",
    "    g.Set(x+3, y+1)\n",
    "    g.Set(x, y+2)\n",
    "    g.Set(x+1, y+2)\n",
    "    g.Set(x+2, y+2)\n",
    "    \n",
    "    x, y = 11, 1\n",
    "    g.Set(x, y)\n",
    "    g.Set(x+1, y)\n",
    "    g.Set(x, y+1)\n",
    "    g.Set(x+1, y+1)\n",
    "    g.Set(x+2, y+2)\n",
    "    g.Set(x+3, y+2)\n",
    "    g.Set(x+2, y+3)\n",
    "    g.Set(x+3, y+3)\n",
    "    \n",
    "    c.DisplayAnimation(20, 250*time.Millisecond)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Gliders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Generation: 300"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img style=\"width:480px;height:480px\" src=\"\">"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "func leftRotate(g *boardImpl) *boardImpl {\n",
    "    w, h := g.Size()\n",
    "    n := NewBoard(h, w)\n",
    "    n.generation = g.generation\n",
    "    for i := 0; i < w; i++ {\n",
    "        for j := 0; j < h; j++ {\n",
    "            n.cur[j][h-1-i] = g.cur[i][j]\n",
    "        }\n",
    "    }\n",
    "    return n\n",
    "}\n",
    "\n",
    "func addGlider(g Board, x, y int) {\n",
    "    g.Set(x, y+2)\n",
    "    g.Set(x+1, y)\n",
    "    g.Set(x+1, y+2)\n",
    "    g.Set(x+2, y+1)\n",
    "    g.Set(x+2, y+2)\n",
    "}\n",
    "\n",
    "{   \n",
    "    g := NewBoard(160, 160)\n",
    "    for r := 0; r < 4; r++ {\n",
    "        max := 7\n",
    "        for i := 0; i < max; i++ {\n",
    "            for j := 0; j < max; j++ {\n",
    "                if i + j >= max {\n",
    "                    continue\n",
    "                }\n",
    "                addGlider(g, i*10+5, j*10+5)\n",
    "            }   \n",
    "        }\n",
    "        g = leftRotate(g)\n",
    "    }\n",
    "    c := NewCanvas(g, 480, 480)\n",
    "    c.DisplayAnimation(300, 100*time.Millisecond)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Random"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Generation: 500"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<img style=\"width:480px;height:480px\" src=\"\">"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import (\n",
    "    \"math/rand\"\n",
    ")\n",
    "\n",
    "{   \n",
    "    w, h := 200, 200\n",
    "    g := NewBoard(w, h)\n",
    "    c := NewCanvas(g, 480, 480)\n",
    "    for i := 1; i < w; i++ {\n",
    "        for j := 1; j < h; j++ {\n",
    "            if rand.Int()%2!=0 {\n",
    "                g.Set(i, j)\n",
    "            }\n",
    "        }\n",
    "    }\n",
    "    \n",
    "    for i := 0; i < 10; i++ {\n",
    "        for j := 0; j < 10; j++ {\n",
    "            addGlider(g, i*8, j*9)\n",
    "        }\n",
    "    }\n",
    "    c.DisplayAnimation(500, 100*time.Millisecond)\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Go (lgo)",
   "language": "go",
   "name": "lgo"
  },
  "language_info": {
   "file_extension": "",
   "mimetype": "",
   "name": "go",
   "version": ""
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
